/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "SkyWaterEffect.h"

#include "CameraObject.h"

#include "Hydrax.h"
#include "Noise/Perlin/Perlin.h"
#include "Modules/ProjectedGrid/ProjectedGrid.h"

#include "SkyX.h"

namespace PQClient
{
	// Current shadow mode
	int __shadowMode = static_cast<int>(SM_LOW);

	// Hydrax pointer,here for i don't want to expose the implement
	SkyX::SkyX* __skyX=0;
	bool __composite=true;//need composite?

	class BloomListener: public Ogre::CompositorInstance::Listener
	{
	public:
		void notifyMaterialSetup(Ogre::uint32 pass_id, Ogre::MaterialPtr &mat)
		{
		}

		void notifyMaterialRender(Ogre::uint32 pass_id, Ogre::MaterialPtr &mat)
		{
			if (pass_id == 3){
				float bloomStrength = 0.75 + Ogre::Math::Clamp<Ogre::Real>(-__skyX->getAtmosphereManager()->getSunDirection().y, 0, 1)*0.75;
				mat->getTechnique(0)->getPass(0)->getFragmentProgramParameters()->setNamedConstant("uBloomStrength", bloomStrength);
			}
		}
	};


	// Hydrax pointer,here for i don't want to expose the implement
	Hydrax::Hydrax *__hydrax = 0;

	class HydraxRttListener : public Hydrax::RttManager::RttListener
	{
	public:
		void preRenderTargetUpdate(const Hydrax::RttManager::RttType& Rtt)
		{
			// If needed in any case...
			bool underwater = __hydrax->_isCurrentFrameUnderwater();

			switch (Rtt)
			{
			case Hydrax::RttManager::RTT_REFLECTION:
				{
					// No stars in the reflection map
					__skyX->setStarfieldEnabled(false);
				}
				break;

			case Hydrax::RttManager::RTT_REFRACTION:
				{
				}
				break;

			case Hydrax::RttManager::RTT_DEPTH: case Hydrax::RttManager::RTT_DEPTH_REFLECTION:
				{
					// Hide SkyX components in depth maps
					__skyX->getMeshManager()->getEntity()->setVisible(false);
					__skyX->getMoonManager()->getMoonBillboard()->setVisible(false);
				}
				break;
			}
		}

		void postRenderTargetUpdate(const Hydrax::RttManager::RttType& Rtt)
		{
			bool underwater =__hydrax->_isCurrentFrameUnderwater();

			switch (Rtt)
			{
			case Hydrax::RttManager::RTT_REFLECTION:
				{
					__skyX->setStarfieldEnabled(true);
				}
				break;

			case Hydrax::RttManager::RTT_REFRACTION:
				{
				}
				break;

			case Hydrax::RttManager::RTT_DEPTH: case Hydrax::RttManager::RTT_DEPTH_REFLECTION:
				{
					__skyX->getMeshManager()->getEntity()->setVisible(true);
					__skyX->getMoonManager()->getMoonBillboard()->setVisible(true);
				}
				break;
			}
		}
	};
	

	WaterEffect::WaterEffect(PQEngine::SceneObject* sceneObject) :
	_sceneObject(sceneObject)
	{
		
		createWater();
		

		createSky();

	}

	void WaterEffect::update(float deltaT)
	{

		updateLighting();

		__hydrax->update(deltaT);
		__skyX->update(deltaT);

		
	}

	void WaterEffect::updateLighting()
	{
		Ogre::Vector3 lightDir = __skyX->getAtmosphereManager()->getSunDirection();

		/*
		bool preForceDisableShadows = mForceDisableShadows;
		mForceDisableShadows = (lightDir.y > 0.15f) ? true : false;

		if (preForceDisableShadows != mForceDisableShadows)
		{
			setShadowMode(mSceneMgr, static_cast<ShadowMode>(mShadowMode));
		}
		*/

		// Calculate current color gradients point
		float point = (-lightDir.y + 1.0f) / 2.0f;
		__hydrax->setWaterColor(_waterGradient.getColor(point));

		Ogre::Vector3 camPos=_sceneObject->getCameraObject()->getDerivedPosition();

		Ogre::Vector3 sunPos=camPos-
			lightDir*__skyX->getMeshManager()->getSkydomeRadius()*0.1;

		__hydrax->setSunPosition(sunPos);

		_light0->setPosition(camPos-
			lightDir*__skyX->getMeshManager()->getSkydomeRadius()*0.02);

		_light1->setDirection(lightDir);

		Ogre::Vector3 sunCol = mSunGradient.getColor(point);
		_light0->setSpecularColour(sunCol.x, sunCol.y, sunCol.z);
		Ogre::Vector3 ambientCol = mAmbientGradient.getColor(point);
		_light0->setDiffuseColour(ambientCol.x, ambientCol.y, ambientCol.z);
		__hydrax->setSunColor(sunCol);
	}

	void WaterEffect::createSky()
	{
		// Color gradients
		// Water
		createGradient();


		Ogre::SceneManager* mgr=_sceneObject->getSceneManager();
		PQEngine::CameraObject* cameraObj=_sceneObject->getCameraObject();
		Ogre::Camera* camera=cameraObj->getCamera();

		// Create SkyX object
		__skyX=new SkyX::SkyX(mgr,camera);
		// No smooth fading
		__skyX->getMeshManager()->setSkydomeFadingParameters(false);
		// A little change to default atmosphere settings :)
		SkyX::AtmosphereManager::Options opt=__skyX->getAtmosphereManager()->getOptions();

		opt.RayleighMultiplier = 0.003075f;//rayleigh 
		opt.MieMultiplier=0.00125f;//
		opt.InnerRadius=9.92f;
		opt.OuterRadius=10.3311f;
		__skyX->getAtmosphereManager()->setOptions(opt);

		//create the sky
		__skyX->create();

		//Add a basic cloud layer
		__skyX->getCloudsManager()->add(SkyX::CloudLayer::Options());


		Ogre::Viewport* viewport=_sceneObject->getRenderWindow()->getViewport(0);

		//Bloom compositor
		Ogre::CompositorInstance* compositor=
			Ogre::CompositorManager::getSingleton().addCompositor(
			viewport,"Bloom");
		compositor->addListener(new BloomListener());
		Ogre::CompositorManager::getSingleton().setCompositorEnabled(
			viewport,"Bloom",__composite);

		//Shadows
		mgr->setShadowCameraSetup(
			Ogre::ShadowCameraSetupPtr(new Ogre::FocusedShadowCameraSetup()));
		mgr->setShadowTextureCasterMaterial("ShadowCaster");

		// Light
		_light0 = mgr->createLight("Light0");
		_light0->setDiffuseColour(1, 1, 1);
		_light0->setCastShadows(false);

		// Shadow caster
		_light1 = mgr->createLight("Light1");
		_light1->setType(Ogre::Light::LT_DIRECTIONAL);

		_light1->setShadowFarDistance(1750);
		setShadowMode(mgr,static_cast<ShadowMode>(__shadowMode));

	}

	void WaterEffect::createWater()
	{
		Ogre::SceneManager *sceneMgr=_sceneObject->getSceneManager();

		Ogre::Camera* cam=_sceneObject->getCameraObject()->getCamera();
		Ogre::Viewport* viewport=_sceneObject->getCameraObject()->getViewport();

		// Create Hydrax object
		__hydrax = new Hydrax::Hydrax(sceneMgr, cam, viewport);

		Hydrax::Module::ProjectedGrid::Options ops;//(128,25,50,false,false,false,3.75f);
		// Create our projected grid module  
		Hydrax::Module::ProjectedGrid *mModule 
			= new Hydrax::Module::ProjectedGrid(// Hydrax parent pointer
			__hydrax,
			// Noise module
			new Hydrax::Noise::Perlin(/*Generic one*/),
			// Base plane
			Ogre::Plane(Ogre::Vector3(0,1,0), Ogre::Vector3(0,0,0)),
			// Normal mode
			Hydrax::MaterialManager::NM_RTT,
			// Projected grid options
			ops);

		// Set our module
		__hydrax->setModule(static_cast<Hydrax::Module::Module*>(mModule));

		// Load all parameters from config file
		// Remarks: The config file must be in Hydrax resource group.
		// All parameters can be set/updated directly by code(Like previous versions),
		// but due to the high number of customizable parameters, since 0.4 version, Hydrax allows save/load config files.
		__hydrax->loadCfg("HydaxCfg.hdx");

		// Create water
		__hydrax->create();

		//// Add the Hydrax depth technique to island material
		__hydrax->getMaterialManager()->addDepthTechnique(
			static_cast<Ogre::MaterialPtr>(Ogre::MaterialManager::getSingleton().getByName("Island"))->createTechnique());



		// Add the Hydrax Rtt listener
		__hydrax->getRttManager()->addRttListener(new HydraxRttListener());

	}


	void WaterEffect::setShadowMode( Ogre::SceneManager* mgr, ShadowMode shadowMode )
	{
		Ogre::MaterialManager::getSingleton().getByName("Island");

		Ogre::MaterialPtr islandMat=static_cast<Ogre::MaterialPtr>(
			Ogre::MaterialManager::getSingleton().getByName("Island"));
		
		/*

		if (mShadowConfigList[smode].Enable && !mForceDisableShadows)
		{
			sm->setShadowTechnique(Ogre::SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED);
			sm->setShadowTextureConfig(0, mShadowConfigList[smode].Size, mShadowConfigList[smode].Size, Ogre::PF_X8R8G8B8);
			IslandMat->getTechnique(0)->setSchemeName("Default");
			IslandMat->getTechnique(1)->setSchemeName("NoDefault");
		}
		else
		{
			sm->setShadowTechnique(Ogre::SHADOWTYPE_NONE);
			IslandMat->getTechnique(0)->setSchemeName("NoDefault");
			IslandMat->getTechnique(1)->setSchemeName("Default");
		}
		*/
	}

	void WaterEffect::createGradient()
	{
		_waterGradient = SkyX::ColorGradient();
		_waterGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.058209,0.535822,0.779105)*0.4, 1));
		_waterGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.058209,0.535822,0.729105)*0.3, 0.8));
		_waterGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.058209,0.535822,0.679105)*0.25, 0.6));
		_waterGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.058209,0.535822,0.679105)*0.2, 0.5));
		_waterGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.058209,0.535822,0.679105)*0.1, 0.45));
		_waterGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.058209,0.535822,0.679105)*0.025, 0));

		// Sun
		mSunGradient = SkyX::ColorGradient();
		mSunGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.8,0.75,0.55)*1.5, 1.0f));
		mSunGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.8,0.75,0.55)*1.4, 0.75f));
		mSunGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.8,0.75,0.55)*1.3, 0.5625f));
		mSunGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.6,0.5,0.2)*1.5, 0.5f));
		mSunGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.5,0.5,0.5)*0.25, 0.45f));
		mSunGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(0.5,0.5,0.5)*0.01, 0.0f));
		// Ambient
		mAmbientGradient = SkyX::ColorGradient();
		mAmbientGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(1,1,1)*1, 1.0f));
		mAmbientGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(1,1,1)*1, 0.6f));
		mAmbientGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(1,1,1)*0.6, 0.5f));
		mAmbientGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(1,1,1)*0.3, 0.45f));
		mAmbientGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(1,1,1)*0.1, 0.35f));
		mAmbientGradient.addCFrame(SkyX::ColorGradient::ColorFrame(Ogre::Vector3(1,1,1)*0.05, 0.0f));
	}


}
